/*
 * BITSI INPUT / OUTPUT
 * by Erik van den Boogert & Bram Daams
 *
 * $Id: bitsi.pde 83 2010-11-25 16:00:23Z e.vandenboogert@donders.ru.nl $
 * $Id: bitsi.pde 83 2010-11-25 16:00:23Z e.vandenboogert@donders.ru.nl $
 *
 */

// V83 2010-11-25 The directive BITSISHIELD was added to support inversion of the buttons on the shield,
//                being active LOW instead of active HIGH like the real button response boxes in the labs.
//                For the normal BITSI's in the labs, BITSISHIELD should NOT be defined.
#define BITSISHIELD

unsigned long TimeoutPulseLength = 10;  /* PulseLength in ms                 */
unsigned long TimeoutDebounce    = 8;   /* Debounce interval in ms           */
unsigned long TimeoutPacingLED   = 500; /* Onboard Pacing LED interval in ms */

/* Reserved hardware wiring for enabling/disabling the HCT244 input buffer to I[8] */
int pinEnableBufIn = 13;

/* Reserved hardware wiring for forcing 27KOhm pull-up / pull-down to I[8] */
int pinPullUpDown  = 19;

/* There are 8 associated input bits D10=pin10, D11=pin11, D12=pin12
 * A0=pin14/AI0, A1=pin15/AI1 ,A2=pin16/AI2, A3=pin17/AI3, A4=pin18/AI3 */
int I[8] = {
  10, 11, 12, 14, 15, 16, 17, 18};

/* declare SerialInChr to receive the desired Parallel_Out value in */
byte SerialInChr;

/* declare general time variables for timing and pacingLED */
unsigned long TimeNow = 0;

/* declare a time varable to remember the time when the Parallel_Out was set and to compare with TimeNow,
 * this way Parallel_Out can be implemented as a pulse with a desireded fixed pulseLength */
unsigned long TimeOnsetOut = 0;
boolean       Parallel_Out_Active = false;

/* declare bit state memory booleans to find out if input values have changed since the last looptest */
boolean State_I[8]      = { 
  LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};
boolean Prev_State_I[8] = {
  HIGH, HIGH,  HIGH, HIGH,  HIGH, HIGH,  HIGH, HIGH
};

/* declare 8 debouncing varables for each input bit.
 * One should remember the time moment when the edge was detected and set a boolean Debouncing_I[n] */
unsigned long TimeOnSet_I[8]  = {
  0, 0, 0, 0, 0, 0, 0, 0};
boolean       Debouncing_I[8] = {
  false, false, false, false, false, false, false, false};


void setup()
{
  // configure input and output ports
  OutPortOutputMode();
  OutPortWrite(0);

  InPortInputMode();

  // initialize serial port
  Serial.begin(115200);


  // configure onboard devices

  // enable input buffer
  pinMode(pinEnableBufIn, OUTPUT);
  digitalWrite(pinEnableBufIn, LOW);

  // enable pullup resistors
  pinMode(pinPullUpDown, OUTPUT);
  digitalWrite(pinPullUpDown, LOW);

  // blink stim leds
  for (byte i = 0; i < 8; i++) {
    OutPortWrite((B00000001 << i + 1 ) - 1);
    delay(100);
  }
  delay(100);

  // show program name and version number
  Serial.println("BITSI $Revision: 83 $, Ready!");
  OutPortWrite(0);
}



void loop()
{
  byte index = 0;

  /* Get TimeNow as a reference for all Timeout counters for debouncing inputs and output pulseLength */
  TimeNow = millis();

  /* Process incoming serial characters
   *
   * The output bits are set immediately to reflect the value of the incoming serial character.
   * After [TimeoutPulseLength] miliseconds the output port is reset to 0.
   */

  /* check if data has been sent from the computer */
  if (Serial.available()) {
    //--- read the most recent byte
    SerialInChr = Serial.read();

#ifdef BITSISHIELD    
    SerialInChr = !(SerialInChr); // Invert all input bits in case of BITSI button shield
#endif

    OutPortWrite(SerialInChr);
    /* store the TimeOnSet moment as a reference timestamp for the desired fixed pulseLength */
    TimeOnsetOut = TimeNow;
    /* Set the Active flag, which enables the pulseLength timeout and returns the outputs to 0
     * after the timeout */
    Parallel_Out_Active = true;
  }
  else {
    if (Parallel_Out_Active & ((TimeNow - TimeOnsetOut) > TimeoutPulseLength)) {
      /* return output bits to zero and reset Active flag */
      OutPortWrite(0);
      Parallel_Out_Active = false;
    }
  }

  /* Process input bits
   *
   * The input bits are monitored for state changes. If bit 0 goes from low to high, a capital 'A' character
   * is sent back to the PC over the serial line.
   * When it changes back from high to low, a lowercase 'a' character is sent to the PC.
   * For bits 0 to 7, the characters A-H and a-h are sent respectively.
   *
   * It is possible to connect mechanical switches to each of the input, because the input bits are debounced.
   * After a bit changes state, it will be ignored for a debouncing interval of [TimeoutDebounce] miliseconds.
   */

  /* loop over the bits, to check their states */
  for (index = 0; index < 8; index = index + 1) {
    State_I[index] = digitalRead(I[index]);
    /* check for bit state change = egde, but not within debouncing interval */
    if ((Prev_State_I[index] != State_I[index]) & !Debouncing_I[index]) {
      /* respond with the corresponding character */
      if (State_I[index] == HIGH) {
        Serial.print(char(97 + index));
      }
      else {
        Serial.print(char(65 + index));
      }

      /* start debouncing */
      TimeOnSet_I[index] = millis();
      Debouncing_I[index] = true;

      /* save new previous bit state */
      Prev_State_I[index] = State_I[index];
    }
  }

  /* reset debouncing status for each bit if the debounce interval has elapsed */
  for (index = 0; index < 8; index = index + 1) {
    if (Debouncing_I[index] & ((TimeNow - TimeOnSet_I[index]) > TimeoutDebounce)) {
      Debouncing_I[index] = false;
    }
  }
}

// todo: implement writing to the input port
void InPortWrite(byte code)
{

}

void OutPortWrite(byte code)
{
  byte b, d;

  // perform bit calculations before updating the ports
  b = (code & B11000000) >> 6 | (PINB & B11111100);
  d = (code & B00111111) << 2;

  // update the ports
  PORTB = b;
  PORTD = d;
}

void OutPortOutputMode()
{
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}

void InPortOutputMode()
{
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(14, OUTPUT);
  pinMode(15, OUTPUT);
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
  pinMode(18, OUTPUT);
}

void InPortInputMode()
{
  // configure all the pins of the input port as inputs
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  pinMode(12, INPUT);
  pinMode(14, INPUT);
  pinMode(15, INPUT);
  pinMode(16, INPUT);
  pinMode(17, INPUT);
  pinMode(18, INPUT);

  // enable/disable internal pullup
  digitalWrite(10, HIGH);
  digitalWrite(11, HIGH);
  digitalWrite(12, HIGH);
  digitalWrite(14, HIGH);
  digitalWrite(15, HIGH);
  digitalWrite(16, HIGH);
  digitalWrite(17, HIGH);
  digitalWrite(18, HIGH);
}

void OutPortInputMode()
{
  // configure all the pins of the output port as inputs ;-)
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);

  // enable internal pullup
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  digitalWrite(8, HIGH);
  digitalWrite(9, HIGH);
}


byte readInPort()
{
  byte b, c;

  b = PINB;
  c = PINC;

  return ((b & B00011100) >> 2) |
    ((c & B00011111)      << 3);
  /*          ^^^^^^^^  */
}

byte readOutPort()
{
  byte c, d;

  c = PINC;
  d = PIND;

  return ((d & B11111100) >> 2) |
    ((c & B00000011) << 6);
  /*          ^^^^^^^^  */
}
